//
// Created by zhengbo on 23-4-18.
//

#include <memory>

#ifndef VISUALIZATION_TOOL_RENDERWINDOW_H
#define VISUALIZATION_TOOL_RENDERWINDOW_H

class Window {
public:
    enum WindowType {ANDROID, WINDOWS, LINUX};

    WindowType GetType() const { return mWindowType; }
    int GetWidth() const { return mWidth; }
    int GetHeight() const { return mHeight; }

    static std::unique_ptr<Window> Create();
    explicit Window(WindowType type) : mWindowType(type) {}
    virtual ~Window() {}

    void SetWidth(int width) { mWidth = width; }
    void SetHeight(int height) { mHeight = height; }

    virtual void Init() = 0;
    virtual void Destroy() = 0;
    virtual bool IsOpen() = 0;
    virtual void SwapBuffers() = 0;
    virtual void Resize(int width, int height) = 0;
    virtual void SetVsync(bool enabled) = 0;
    virtual void* GetHandle() = 0;

    virtual void MakeCurrent() = 0;
    virtual void UnMakeCurrent() = 0;


protected:
    WindowType mWindowType;
    int mWidth = 640;
    int mHeight = 480;
};

#ifdef __ANDROID__
// AndroidWindow.h
#include <jni.h>
#include <EGL/egl.h>
#include <EGL/eglext.h>
#include <GLES2/gl2.h>
#include <android/native_window.h>
#include <iostream>

class AndroidWindow : public Window {
public:
    AndroidWindow() : Window(ANDROID) {}

    ~AndroidWindow() {
        // Close Android mWindow
        if (mWindow != nullptr) {
            ANativeWindow_release(mWindow);
        }
    }

    virtual void Init() {
        InitEGL(mWindow);
    }

    void Destroy() override {
        DestroyEGL();
    }

    bool IsOpen() override {
        return true;
    }

    void SwapBuffers() override {
        eglSwapBuffers(mDisplay, mSurface);
    }

    void Resize(int width, int height) override {
        std::cout << "not support to resize the resolution on Android" << std::endl;
    }

    void SetVsync(bool enabled) override {
        if (mWindow == nullptr) {
            std::cout << "now window set, it's headless mode" << std::endl;
            return;
        }
        int vsync = enabled ? 1 : 0;
        ANativeWindow_setSwapInterval(mWindow, vsync);
    }

    void* GetHandle() override {
        return mWindow;
    }

    void MakeCurrent() override {
        // Make current
        eglMakeCurrent(mDisplay, mSurface, mSurface, mContext);
    }

    void UnMakeCurrent() override {
        // Make current
        eglMakeCurrent(mDisplay, EGL_NO_SURFACE, EGL_NO_SURFACE, mContext);
    }

    void SetSurface(void* surface, void* env) {
        if (mWindow != nullptr) {
            ANativeWindow_release(mWindow);
        }
        mWindow = ANativeWindow_fromSurface((JNIEnv*)env, (jobject)surface);
    }
private:
    void InitEGL(ANativeWindow* window) {
        // Get EGL mDisplay
        mDisplay = eglGetDisplay(EGL_DEFAULT_DISPLAY);
        // Initialize EGL
        eglInitialize(mDisplay, NULL, NULL);

        // Choose EGL config
        int configAttribs[] = {
                EGL_RENDERABLE_TYPE, EGL_OPENGL_ES2_BIT,
                EGL_RED_SIZE, 8,
                EGL_GREEN_SIZE, 8,
                EGL_BLUE_SIZE, 8,
                EGL_ALPHA_SIZE, 8,
                EGL_DEPTH_SIZE, 16,
                EGL_NONE
        };
        EGLint numConfigs;
        EGLConfig config;
        eglChooseConfig(mDisplay, configAttribs, &config, 1, &numConfigs);

        if (window != nullptr) {
        // Create EGL mSurface
            mSurface = eglCreateWindowSurface(mDisplay, config, window, NULL);
        } else {
            EGLint pbufferAttribs[] = {
                    EGL_WIDTH, mWidth,
                    EGL_HEIGHT, mHeight,
                    EGL_NONE
            };
            mSurface = eglCreatePbufferSurface(mDisplay, config, nullptr);
        }
        // Create EGL mContext
        EGLint contextAttribs[] = {
                EGL_CONTEXT_CLIENT_VERSION, 2,
                EGL_NONE
        };
        mContext = eglCreateContext(mDisplay, config, EGL_NO_CONTEXT, contextAttribs);
    }

    void DestroyEGL() {
        eglDestroySurface(mDisplay, mSurface);
        eglDestroyContext(mDisplay, mContext);
        eglTerminate(mDisplay);
    }
private:
    ANativeWindow* mWindow = nullptr;
    EGLDisplay mDisplay = EGL_NO_DISLPAY;
    EGLSurface mSurface = EGL_NO_SURFACE;
    EGLContext mContext = EGL_NO_CONTEXT;
};

std::unique_ptr<Window> Window::Create() {
    return std::make_unique<AndroidWindow>();
}

#else

#include <GL/gl.h>
#include <GLFW/glfw3.h>

class GWindow : public Window {
public:
    GWindow() : Window(LINUX) {}

    void Init() override {
        // Initialize GLFW
        glfwInit();
        glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 3);
        glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 3);
        mWindow = glfwCreateWindow(mWidth, mHeight, "RenderWindow", nullptr, nullptr);
    }

    void Destroy() override {
        glfwDestroyWindow(mWindow);
        glfwTerminate();
    }

    ~GWindow() override {
        // Properly close GLFW mWindow
        glfwDestroyWindow(mWindow);
        glfwTerminate();
    }

    bool IsOpen() override {
        return !glfwWindowShouldClose(mWindow);
    }

    void SwapBuffers() override {
        glfwSwapBuffers(mWindow);
    }

    void Resize(int width, int height) override {
        glfwSetWindowSize(mWindow, width, height);
        mWidth = width;
        mHeight = height;
    }

    void SetVsync(bool enabled) override {
        int vsync = enabled ? 1 : 0;
        glfwSwapInterval(vsync);
    }

    void* GetHandle() override {
        return mWindow;
    }

    void MakeCurrent() override {
        glfwMakeContextCurrent(mWindow);
    }

    void UnMakeCurrent() override {
        glfwMakeContextCurrent(nullptr);
    }

private:
    GLFWwindow* mWindow = nullptr;
};

std::unique_ptr<Window> Window::Create() {
    auto window = std::make_unique<GWindow>();
    return window;
}


#endif // __ANDROID__
#endif // VISUALIZATION_TOOL_RENDERWINDOW_H
